/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.sarco.bwi.lib;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Timer;

/**
 *
 * @author iktsup
 */
public final class Master {

    public static final String DEFAULT_CONFIG = "master.config";
    private String status;

    private Config config;
    private ServerSocket master;
    private int nodesDone = 0;
    private Timer timerMessages;
    private boolean useMysql = false;
   
    private static final String MSG_WAIT_FOR_CONN    = "Waiting for connections!";
    private static final String MSG_GOT_CONN         = "Got connection!";
    private static final String MSG_STARTING_JOB     = "Starting job!";
    private static final String MSG_SHARING_JOBS     = "Sharing render jobs!";

    public boolean allNodes = false;
    private Node[] nodes;

    private ArrayList<String> messages = new ArrayList<String>();

    /**
     * The default constructor.
     * @param config The config to load.
	 * @see com.sarco.bwi.Config
     */
    public Master(Config config) {
        if (config != null) {
            try {
                this.config = config;
                master = new ServerSocket(this.getConfig().getPort());
                this.useMysql = this.config.isUseMysql();
            } catch (UnknownHostException ex) {
                Logger.getLogger(Master.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(Master.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    /**
     * Wait for all nodes to connect.
     */
    public void waitForConnections() {
        int iNodes = this.getConfig().getNodes().length;
        this.nodes = new Node[iNodes];
        for (int i = 0; i < iNodes; i++) {
            try {
                System.out.println(Master.MSG_WAIT_FOR_CONN);
                Data.getMessages().add(Master.MSG_WAIT_FOR_CONN);

                status = Master.MSG_WAIT_FOR_CONN;

                Socket client = this.getMaster().accept();
                System.out.println(Master.MSG_GOT_CONN);
                Data.getMessages().add(Master.MSG_GOT_CONN);

                status = Master.MSG_GOT_CONN;

                Node n = new Node();
                n.setClient(client);
                n.setIn(client.getInputStream());
                n.setOut(client.getOutputStream());
                this.nodes[i] = n;
            } catch (IOException ex) {
                Logger.getLogger(Master.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        System.out.println("Got all nodes!");
        Data.getMessages().add("Got all nodes!");
        this.allNodes = true;

        System.out.println(Master.MSG_STARTING_JOB);
        Data.getMessages().add(Master.MSG_STARTING_JOB);

        status = Master.MSG_STARTING_JOB;

        this.startJob();
    }

    /**
     * Starts the render job<br>
	 * Invokes <code>this.shareRenderJobs();</code>
	 * @see com.sarco.bwi.Master
     */
    public void startJob() {
        System.out.println(Master.MSG_SHARING_JOBS);
        Data.getMessages().add(Master.MSG_SHARING_JOBS);

        status = Master.MSG_SHARING_JOBS;

        this.shareRenderJobs();
        this.initTimers();
    }

    /**
     * Shares the render jobs to each node.<br>
	 * The render job is split equally between each node, if it can't be split 100% equal; The last
	 * node will get the most frames to render (surplus 1-4(+) frames).
     */
    public void shareRenderJobs() {
        double framesEach = this.getConfig().getEndFrame() / this.getNodes().length;
        System.out.println("Frames each: " + framesEach);
        System.out.println("Nodes: " + this.getNodes().length);
        for (int i = 0; i < this.getNodes().length; i++) {
            try {
                byte[] bOut = new byte[128];
                int startFrame = (int) (this.getConfig().getStartFrame() +
                        (framesEach * i));
                int endFrame = (int) (startFrame + framesEach);
                if (i == this.getNodes().length -1)
                    endFrame = this.getConfig().getEndFrame();
                else
                    endFrame--;
                String s = Node.MASTER_SHARE_JOBS + startFrame
                        + Node.COMMON_SPLIT
                        + endFrame
                        + Node.COMMON_SPLIT;
                bOut = s.getBytes();
                this.getNodes()[i].getOut().write(bOut);
                this.getNodes()[i].getOut().flush();

                this.getNodes()[i].setStartFrame(startFrame);
                this.getNodes()[i].setEndFrame(endFrame);
            } catch (IOException ex) {
                Logger.getLogger(Master.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    private void initTimers() {
        status = "START: initTimers()";
        {
            ActionListener msgListener = new ActionListener() {
                public void actionPerformed(ActionEvent evt) {
                    try {
                        waitForMessages();
                    } catch (NullPointerException ex) {
                        
                    }
                }
            };

            this.timerMessages = new Timer(1, msgListener);

            this.timerMessages.start();
        }
        status = "DONE: initTimers()";
    }

    private void waitForMessages() throws NullPointerException {
            for (int i = 0; i < getNodes().length; i++) {
                try {
                    String s = "";
                    byte[] bIn = new byte[512];
                    getNodes()[i].getIn().read(bIn);
                    for (int y = 0; y < bIn.length; y++) {
                        s += (char) bIn[y];
                    }
                    
                    s = s.trim();

                    if (s.startsWith(Node.NODE_STATUS)) {
                        String msg = s.substring(Node.NODE_STATUS.length());

                        String[] sStatus = msg.split(Node.COMMON_SPLIT);

                        getNodes()[i].setCurrFrame(Integer.parseInt(sStatus[0]));

                        double dPercentDone = Double.parseDouble(sStatus[1]);
                        getNodes()[i].setiPercentDone((int) dPercentDone);

                        getNodes()[i].setTimeLeft(sStatus[2]);
                    } else if (s.startsWith(Node.NODE_DONE)) {
                        byte[] bOut = new byte[128];
                        bOut = Node.MASTER_TERMINATE.getBytes();
                        nodesDone++;
                        getNodes()[i].setCurrFrame(getNodes()[i].getEndFrame());
                        getNodes()[i].getOut().write(bOut);
                        getNodes()[i].getOut().flush();
                    } else {
                        // System.out.println(s);
                    }
                } catch (IOException ex) {
                }
            }
        }

    /**
     *
     * @return
     */
    public String[] getMessages() {
        return (String[]) this.messages.toArray();
    }

    /**
     * @return the status
     */
    public String getStatus() {
        return status;
    }

    /**
     * @return the config
     */
    public Config getConfig() {
        return config;
    }

    /**
     * @return the master
     */
    public ServerSocket getMaster() {
        return master;
    }

    /**
     * @return the nodesDone
     */
    public int getNodesDone() {
        return nodesDone;
    }

    /**
     * @return the useMysql
     */
    public boolean isUseMysql() {
        return useMysql;
    }

    /**
     * @return the nodes
     */
    public Node[] getNodes() {
        return nodes;
    }

    public void closeAllConnections() throws IOException {
        this.timerMessages.stop();
        this.timerMessages = null;
        for (int i = 0; i < nodes.length; i++) {
            nodes[i].getIn().close();
            nodes[i].getOut().close();
            nodes[i].setIn(null);
            nodes[i].setOut(null);
            
            nodes[i] = null;
        }
        
        master.close();
        master = null;
    }
}
